# -*- coding: utf-8 -*-

from hlm.node import *

from ip.peer import NodePeer
from ip.mrunner import NodeMRunner
from ip.fabrics import ChannelLoadFabric, ChannelStoreFabric, ChannelExecOpFabric
from ip.router_nanoop import NodeRouterNanoOp
from ip.router_load import NodeRouterLoad
from ip.arbiter_store import NodeArbiterStore
from ip.and_gate import NodeAndGate


class NodePeerGroup(Node):
    def initialize(self, params: ObjectDict) -> None:
        params.peer_id_base: int
        params.num_of_peer: int
        params.softmem_size: int
        params.num_of_main_memory: int
        params.num_of_alu: int
        params.multiplexer: int
        params.total_peers: int
        params.base_ptrs: 'List.int'
        # Fetch the peer ids
        self.const.peer_ids = [params.peer_id_base + ii for ii in range(params.num_of_peer)]
        # Create nodes for peer group.
        self.node.peers = [self.node_create(
            NodePeer,
            {'peer_id': self.const.peer_ids[ii],
             'softmem_size': params.softmem_size,
             'num_of_main_memory': params.num_of_main_memory,
             'num_of_alu': params.num_of_alu,
             'multiplexer': params.multiplexer,
             'total_peers': params.total_peers,
             'base_ptrs': params.base_ptrs})
            for ii in range(params.num_of_peer)]

        # Build the message chain.
        self.node.mrunner = self.node_create(
            NodeMRunner,
            {'peer_ids': self.const.peer_ids})
        # Connect the message chain.
        for ii in range(params.num_of_peer - 1):
            # Connect the chain.
            ChannelLoadFabric.connect(self.node.peers[ii].port.chain_out,
                                      self.node.peers[ii+1].port.chain_in)
        ChannelLoadFabric.connect(self.node.peers[-1].port.chain_out,
                                  self.node.mrunner.port.message_in)
        ChannelLoadFabric.connect(self.node.mrunner.port.message_out,
                                  self.node.peers[0].port.chain_in)
        # Bypass the M-Runner ports.
        self.port.chain_out = self.port_bypass(self.node.mrunner.port.chain_out)
        self.port.chain_in = self.port_bypass(self.node.mrunner.port.chain_in)

        # Create the op router.
        self.node.router_eop = self.node_create(
            NodeRouterNanoOp,
            {'routing_map': [[ii] for ii in self.const.peer_ids]})
        for ii in range(params.num_of_peer):
            ChannelExecOpFabric.connect(self.node.router_eop.port.senders[ii],
                                        self.node.peers[ii].port.nano_op_receiver)
        # Bypass the receiver.
        self.port.nano_op_receiver = self.port_bypass(self.node.router_eop.port.receiver)

        # Create the memory router.
        self.node.router_load = [
            self.node_create(
                NodeRouterLoad,
                {'multiplexer': params.multiplexer,
                 'routing_map': [[ii] for ii in self.const.peer_ids]})
            for _ in range(params.num_of_main_memory)]
        for ii in range(params.num_of_main_memory):
            # Connect the router to all the peers.
            for jj in range(params.num_of_peer):
                ChannelLoadFabric.connect(self.node.router_load[ii].port.senders[jj],
                                          self.node.peers[jj].port.memory_receivers[ii])
        # Bypass the receivers.
        self.port.memory_receivers = [self.port_bypass(self.node.router_load[ii].port.receiver)
                                      for ii in range(params.num_of_main_memory)]

        # Create the memory arbiter.
        self.node.arbiter_store = [
            self.node_create(NodeArbiterStore,
                             {'num_of_receivers': params.num_of_peer})
            for _ in range(params.num_of_peer)]
        for ii in range(params.num_of_main_memory):
            # Connect the aribiter to peers.
            for jj in range(params.num_of_peer):
                ChannelStoreFabric.connect(self.node.peers[jj].port.memory_senders[ii],
                                           self.node.arbiter_store[ii].port.receivers[jj])
        # Bypass the senders.
        self.port.memory_senders = [self.port_bypass(self.node.arbiter_store[ii].port.sender)
                                    for ii in range(params.num_of_main_memory)]

        # Create the halt detection.
        self.node.halt_detect = self.node_create(
            NodeAndGate,
            {'num_of_ports': params.num_of_peer + params.num_of_main_memory + 1})
        # Connect halt ports.
        for ii in range(params.num_of_peer):
            self.node.peers[ii].port.halt.connect(
                self.node.halt_detect.port.and_ins[ii])
        for ii in range(params.num_of_main_memory):
            self.node.arbiter_store[ii].port.halt.connect(
                self.node.halt_detect.port.and_ins[params.num_of_peer + ii])
        self.node.mrunner.port.halt.connect(
            self.node.halt_detect.port.and_ins[-1])
        # Bypass the halt port.
        self.port.halt = self.port_bypass(self.node.halt_detect.port.and_out)
